using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Markup;
using System.Reflection;
using System.Windows.Data;
using System.Globalization;
using System.Collections.ObjectModel;

namespace ValidationFramework
{
    /// <summary>
    /// Decorates content to allow validation of the bindings
    /// </summary>
    public class ValidatedContent: Decorator
    {
        #region Public Constructors

        /// <summary>
        /// Initializes a new instance of ValidatedContent
        /// </summary>
        public ValidatedContent()
        {
            ErrorMessages = new ObservableCollection<string>();
            
            Loaded += new RoutedEventHandler(OnValidatedContentLoaded);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the loaded event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnValidatedContentLoaded(object sender, RoutedEventArgs e)
        {
            Queue<DependencyObject> elementQueue = new Queue<DependencyObject>();
            elementQueue.Enqueue(this.Child);

            // Iterate over all the child elements
            while (elementQueue.Count > 0)
            {
                // Dequeue the first element in the queue
                DependencyObject element = elementQueue.Dequeue();
                
                foreach (DependencyObject childElement in LogicalTreeHelper.GetChildren(element))
                {
                    elementQueue.Enqueue(childElement);
                }

                Control control = element as Control;

                // Mark the element as valid if it is a control
                if (control != null && GetIsRequired(element))
                {
                    control.SetValue(Control.StyleProperty, RequiredControlStyle);
                }
            }

        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Validates the content of the decorator
        /// </summary>
        public void Validate()
        {
            IsContentValid = true;
            ErrorMessages.Clear();

            Queue<DependencyObject> elementQueue = new Queue<DependencyObject>();
            elementQueue.Enqueue(this.Child);

            // Iterate over all the child elements
            while (elementQueue.Count > 0)
            {
                // Dequeue the first element in the queue
                DependencyObject element = elementQueue.Dequeue();

                foreach (DependencyObject childElement in LogicalTreeHelper.GetChildren(element))
                {
                    elementQueue.Enqueue(childElement);
                }
                
                // Validate the bindings of the element
                ValidateBindings(element);
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Validates the bindings of the dependency object
        /// </summary>
        /// <param name="element"></param>
        private void ValidateBindings(DependencyObject element)
        {
            Type elementType = element.GetType();

            FieldInfo[] dependencyPropertyFields = elementType.GetFields(
                BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly);

            // Iterate over all dependency properties
            foreach (FieldInfo dependencyPropertyField in dependencyPropertyFields)
            {
                DependencyProperty dependencyProperty = 
                    (DependencyProperty)dependencyPropertyField.GetValue(element);

                Binding binding = BindingOperations.GetBinding(element, dependencyProperty);

                // Issue 1822 - Extra check added to prevent null reference exceptions
                if (binding != null)
                {
                    // Validate the validation rules of the binding
                    foreach (ValidationRule rule in binding.ValidationRules)
                    {
                        ValidationResult result = rule.Validate(element.GetValue(dependencyProperty),
                            CultureInfo.CurrentCulture);

                        if (!result.IsValid)
                        {
                            ErrorMessages.Add(result.ErrorContent.ToString());
                        }

                        IsContentValid &= result.IsValid;
                    }
                }
            }
        }

        #endregion

        #region Static Methods

        /// <summary>
        /// Gets the value for the IsRequired attached property
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static bool GetIsRequired(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsRequiredProperty);
        }

        /// <summary>
        /// Sets the value for the IsRequired attached property
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="value"></param>
        public static void SetIsRequired(DependencyObject obj, bool value)
        {
            obj.SetValue(IsRequiredProperty, value);
        } 

        #endregion

        #region Public Properties
        
        /// <summary>
        /// Gets or sets the style to mark a required control
        /// </summary>
        public Style RequiredControlStyle
        {
            get { return (Style)GetValue(RequiredControlStyleProperty); }
            set { SetValue(RequiredControlStyleProperty, value); }
        }

        /// <summary>
        /// Gets or sets the error messages for the validated content
        /// </summary>
        public ObservableCollection<string> ErrorMessages
        {
            get { return (ObservableCollection<string>)GetValue(ErrorMessagesProperty); }
            private set { SetValue(ErrorMessagesProperty, value); }
        }

        /// <summary>
        /// Gets if the content is valid
        /// </summary>
        public bool IsContentValid
        {
            get { return (bool)GetValue(IsContentValidProperty); }
            private set { SetValue(IsContentValidProperty, value); }
        }

        #endregion
        
        #region Dependency Properties

        public static readonly DependencyProperty IsContentValidProperty =
            DependencyProperty.Register("IsContentValid", typeof(bool), 
            typeof(ValidatedContent), new UIPropertyMetadata(false));

        public static readonly DependencyProperty ErrorMessagesProperty =
            DependencyProperty.Register("ErrorMessages", typeof(ObservableCollection<string>),
            typeof(ValidatedContent), new UIPropertyMetadata(null));

        public static readonly DependencyProperty RequiredControlStyleProperty =
            DependencyProperty.Register("RequiredControlStyle", typeof(Style),
            typeof(ValidatedContent), new UIPropertyMetadata(null));

        public static readonly DependencyProperty IsRequiredProperty =
            DependencyProperty.RegisterAttached("IsRequired", typeof(bool),
            typeof(ValidatedContent), new UIPropertyMetadata(false)); 

        #endregion
    }
}
